

#include <stdio.h>
#include <stdlib.h>

#include <sys/select.h>

#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>

#include "fsms/fsmv2.h"
#include "fsms/fsmquickset.h"
#include "device.h"
#include "parser.h"
#include "serial_device.h"
#include "device_test.h"

#include "dump.h"
#include "logging.h"


#define ALOGD  printf


class DeviceTestListener : public DeviceTest::Listener {
public:
    FILE* fp = NULL;
    DeviceTestListener() {
        fp = fopen("result.txt", "wb+");
        if (fp == NULL) {
            perror("fopen");
            exit(1);
        }
    }

    void pass(const char * name) {
        fprintf(fp, "[ pass ] <%s>\n", name);
        printf("[ pass ] <%s>\n", name);
        fflush(fp);
    }

    void fail(const char * name, const char * reason) {
        fprintf(fp, "[ fail ] <%s> (%s)\n", name, reason);
        printf("[ fail ] <%s> (%s)\n", name, reason);
        fflush(fp);
    }

    virtual void onTestCaseResult(const char * name, unsigned char * expected, int expected_len, unsigned char * real, int real_len) {
        if (real_len != expected_len) {
            if (real_len == 0) {
                fail(name, "no response.");
            } else {
                fail(name, "invalid response length.");
            }
            if (expected_len > 0) {
                printf("expected: (%d bytes)\n", expected_len);
                tools::dump(expected, expected_len);
                printf("received: (%d bytes)\n", real_len);
                tools::dump(real, real_len);
            }
            return;
        }
        if (real_len > 0 && memcmp(expected, real, real_len) != 0) {
            fail(name, "Not expected.");
            printf("expected: (%d bytes)\n", expected_len);
            tools::dump(expected, expected_len);
            printf("received: (%d bytes)\n", real_len);
            tools::dump(real, real_len);
            return;
        }

        if (expected_len > 0) {
            printf("received: (%d bytes)\n", real_len);
            tools::dump(real, real_len);
        }

        int ch = 0;
        if (real_len > 0)
            ch = 'y';

        while (ch != 'y' && ch != 'n') {
            printf("TestCase [%s]. did the function work? (y/n)", name);
            char cmd[1024];
            if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
                perror("fgets");
                exit(1);
            }
            ch = cmd[0];
        }
        if (ch == 'y') {
            pass(name);
        } else {
            fail(name, "not work.");
        }
    }
};


class Case {
public:
    const char * name;
    unsigned char   command[100];
    int command_len;
    unsigned char   expected[100];
    int expected_len;
};

void addV1Cases(DeviceTest& dt)
{/*{{{*/
    // const unsigned char a[] = {
        // 0xdd, 0xff,  0x00, 0x05,  0xa0, 0x00, 0x00, 0x00,               0xa5,  0xbb, 0xcc,   // v1 启动
        // 0xdd, 0xff,  0x00, 0x05,  0xa1, 0x01, 0x00, 0x00,               0xa5,  0xbb, 0xcc,   // v1 待机
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x02,        0xYY,  0xbb, 0xcc,   // v1 进入工厂M
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x03,        0xYY,  0xbb, 0xcc,   // v1 退出工厂M
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x04,        0xYY,  0xbb, 0xcc,   // v1 进入老化
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x05,        0xYY,  0xbb, 0xcc,   // v1 退出老化
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x06,        0xYY,  0xbb, 0xcc,   // v1 用户恢复出厂
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x07,        0xYY,  0xbb, 0xcc,   // v1 保护清空
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x08,        0xYY,  0xbb, 0xcc,   // v1 非保护清空
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x02, 0x00, 0x00,  0x09,        0xYY,  0xbb, 0xcc,   // v1 工厂OK
        // 0xdd, 0xff,  0x00, 0x07,  0xa1, 0x03, 0x00, 0x00,  0xXX, 0xXX,  0xYY,  0xbb, 0xcc,   // v1 切换频道
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x04, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换AV1
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x05, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换分量YPbPr
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x06, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换HDMI
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x07, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换VGA
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x08, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换TV/VGA
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x09, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置音量
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0a, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置亮度
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0b, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置对比度
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0c, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置图像模式
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0d, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置色温
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0e, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置内置图卡
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x0f, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置屏BIN版本
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x10, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置LOGO
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x11, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置语言
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x12, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置色度
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x13, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置色调
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x14, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置清晰度
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x15, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置背光亮度
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x16, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置节能模式
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x17, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 设置宽高比
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x19, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 切换SCART
        // 0xdd, 0xff,  0x00, 0x05,  0xa1, 0x1a, 0x00, 0x00,               0xYY,  0xbb, 0xcc,   // v1 色彩优化
        // 0xdd, 0xff,  0x00, 0x06,  0xa1, 0x1b, 0x00, 0x00,  0x00,        0xYY,  0xbb, 0xcc,   // v1 开关3D
        // 0xdd, 0xff,  0x00, 0x05,  0xa2, 0x00, 0x00, 0x00,         0xYY,  0xbb, 0xcc,   // v1 进入DMP
        // 0xdd, 0xff,  0x00, 0x05,  0xa2, 0x01, 0x00, 0x00,         0xYY,  0xbb, 0xcc,   // v1 进入DMP自动播放
        // 0xdd, 0xff,  0x00, 0x0c,  0xa2, 0x02, 0x00, 0x00,  ( 7 )  0xYY,  0xbb, 0xcc,   // v1 写入MAC地址
        // 0xdd, 0xff,  0x00, 0x  ,  0xa2, 0x03, 0x00, 0x00,         0xYY,  0xbb, 0xcc,   // v1 设置有线
        // 0xdd, 0xff,  0x00, 0x  ,  0xa2, 0x04, 0x00, 0x00,         0xYY,  0xbb, 0xcc,   // v1 设置无线
        // 0xdd, 0xff,  0x00, 0x1c,  0xa2, 0x05, 0x00, 0x00,  (23 )  0xYY,  0xbb, 0xcc,   // v1 设置机号
        // 0xdd, 0xff,  0x00, 0x05,  0xa2, 0x1b, 0x00, 0x00,         0xYY,  0xbb, 0xcc,   // v1 退出DMP
    // };
}/*}}}*/

void addV2Cases(DeviceTest& dt)
{/*{{{*/
    Case cases[] = {
        { "V2.AV1",   { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x03, 0x01, 0x00, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x03, 0x01, 0x00, 0xcd, 0xcd }, 10, },      // v2 AV1
        { "V2.AV2",   { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x03, 0x01, 0x01, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x03, 0x01, 0x01, 0xcd, 0xcd }, 10, },      // v2 AV2
        { "V2.分量",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x04, 0x02, 0x00, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x04, 0x02, 0x00, 0xcd, 0xcd }, 10, },      // v2 分量
        { "V2.HDMI",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x00, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x00, 0xcd, 0xcd }, 10, },      // v2 HDMI
        { "V2.DVI",   { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x01, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x01, 0xcd, 0xcd }, 10, },      // v2 DVI
        { "V2.OPS",   { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x02, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x05, 0x03, 0x02, 0xcd, 0xcd }, 10, },      // v2 OPS
        { "V2.VGA1",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x06, 0x04, 0x00, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x06, 0x04, 0x00, 0xcd, 0xcd }, 10, },      // v2 VGA1
        { "V2.VGA2",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x06, 0x04, 0x01, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x06, 0x04, 0x01, 0xcd, 0xcd }, 10, },      // v2 VGA2
        { "V2.音量",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x07, 0x00, 0x10, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x07, 0x00, 0x10, 0xcd, 0xcd }, 10, },      // v2 音量0x10
        { "V2.亮度",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x08, 0x00, 0x10, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x08, 0x00, 0x10, 0xcd, 0xcd }, 10, },      // v2 亮度0x10
        { "V2.对比",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x09, 0x00, 0x10, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0x09, 0x00, 0x10, 0xcd, 0xcd }, 10, },      // v2 对比度0x10
        { "V2.查询",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x10, 0x00, 0x00, 0xbb, 0xcc }, 10, { 0xab, 0xab, 0x01, 0x04, 0xa1, 0xff, 0x00, 0x10, 0xcd, 0xcd }, 10, },      // v2 查询
        // { "开机",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x00, 0x00, 0x00, 0xbb, 0xcc }, 10 },       // v2 开机
        // { "待机",  { 0xdd, 0xff, 0x01, 0x04, 0xa1, 0x01, 0x00, 0x00, 0xbb, 0xcc }, 10 },       // v2 待机
    };

    for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
        // printf("%s\n", cases[i].name);
        // tools::dump(cases[i].command, cases[i].command_len);
        dt.addTestCase(cases[i].name, cases[i].command, cases[i].command_len, cases[i].expected, cases[i].expected_len);
    }
    // void addTestCase(const char * name, unsigned char * command, int command_len, unsigned char * respnose, int response_len);
}/*}}}*/


void calcQuicksetChecksum(unsigned char * command, int len)
{
    unsigned char sum = 0;
    for (int i = 2; i < len - 3; i++) {
        sum ^= command[i];
    }
    command[len - 3] = sum;
}

void addQuicksetCases(DeviceTest& dt)
{/*{{{*/
    Case cases[] = {
        //                       {   header           length             command                id        data               checksum  tail    }
        { "quickset.自动校正",   { 0xdd, 0xff,     0x00, 0x06,     0xc1, 0x01, 0x00, 0x00,     0x00,                         0xc6, 0xbb, 0xcc, }, 12, {}, 0 },
        { "quickset.ADC",        { 0xdd, 0xff,     0x00, 0x06,     0xc1, 0x07, 0x00, 0x00,     0x00,                         0xc0, 0xbb, 0xcc, }, 12, {}, 0 },
        { "quickset.AV1",        { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x01,               0xcf, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.AV2",        { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x02,               0xcc, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.Comp",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x03,               0xcd, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.DVI1",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x04,               0xca, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.DVI2",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x05,               0xcb, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.VGA1",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x06,               0xc8, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.VGA2",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x08, 0x00, 0x00,     0x00,     0x07,               0xc9, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.Loc 0x0-1",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x00, 0x00, 0x01,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 1x1-1",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x01, 0x01, 0x01,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 2x2-1",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x02, 0x02, 0x01,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 2x2-2",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x02, 0x02, 0x02,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 2x2-3",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x02, 0x02, 0x03,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 2x2-4",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x02, 0x02, 0x04,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.Loc 1x1-1",  { 0xdd, 0xff,     0x00, 0x09,     0xc1, 0x0a, 0x00, 0x00,     0x00,     0x01, 0x01, 0x01,   0x00, 0xbb, 0xcc, }, 15, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_L_5%",    { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x00, 0x00,     0x00,     0x05,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_L_10%",   { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x00, 0x00,     0x00,     0x0a,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_L_15%",   { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x00, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_R",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x01, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_LR",      { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x02, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_T",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x03, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_B",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x04, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_TB",      { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x05, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_LRTB",    { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x0f,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.DisplayOn",  { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x02, 0x00,     0x00,     0x01,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.DisplayOff", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x02, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.标准",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x06, 0x00,     0x00,     0x07,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.鲜艳",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x06, 0x00,     0x00,     0x08,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.柔和",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x06, 0x00,     0x00,     0x09,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.电影",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x06, 0x00,     0x00,     0x0a,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.文本",       { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x06, 0x00,     0x00,     0x0b,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.静止开",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x08, 0x00,     0x00,     0x01,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.静止关",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x08, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.除雾开",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x0a, 0x00,     0x00,     0x01,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.除雾关",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x0a, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.低照开",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x0b, 0x00,     0x00,     0x01,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.低照关",     { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0f, 0x0b, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.AEC_WH",     { 0xdd, 0xff,     0x00, 0x0b,     0xc1, 0x11, 0x00, 0x00,     0x00,     0x03, 0xfa, 0x02, 0x42, 0x01,      0x00, 0xbb, 0xcc, }, 17, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.AEC_LR",     { 0xdd, 0xff,     0x00, 0x0b,     0xc1, 0x12, 0x00, 0x00,     0x00,     0x00, 0x10, 0x00, 0x10, 0x01,      0x00, 0xbb, 0xcc, }, 17, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.AEC_TB",     { 0xdd, 0xff,     0x00, 0x0b,     0xc1, 0x13, 0x00, 0x00,     0x00,     0x00, 0x20, 0x00, 0x20, 0x01,      0x00, 0xbb, 0xcc, }, 17, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.AEC_WHLR",   { 0xdd, 0xff,     0x00, 0x0e,     0xc1, 0x21, 0x00, 0x00,     0x00,     0x03, 0xfa, 0x02, 0x42, 0x00, 0x10, 0x00, 0x10,      0x00, 0xbb, 0xcc, }, 20, {}, 0 },
        { "quickset.EC_Restore", { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x0b, 0x06, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.MENU",       { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x00,         0xde, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.UP",         { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x01,         0xdf, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.DOWN",       { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x02,         0xdc, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.LEFT",       { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x03,         0xdd, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.RIGHT",      { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x04,         0xda, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.OK/Enter",   { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x05,         0xdb, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.Return",     { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x06,         0xd8, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.Source",     { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x17, 0x00, 0x00,     0x00,     0x00, 0x07,         0xd9, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.SerialOn",   { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x20, 0x00, 0x00,     0x00,     0x01,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.SerialOff",  { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x20, 0x00, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.恢复出厂",   { 0xdd, 0xff,     0x00, 0x07,     0xc1, 0x10, 0x00, 0x00,     0x00,     0x00,               0x00, 0xbb, 0xcc, }, 13, {}, 0 },
        { "quickset.关机",       { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x15, 0x00, 0x00,     0x00,     0xaa, 0xaa,         0x00, 0xbb, 0xcc, }, 14, {}, 0 },
        { "quickset.开机",       { 0xdd, 0xff,     0x00, 0x08,     0xc1, 0x15, 0x00, 0x00,     0x00,     0xbb, 0xbb,         0x00, 0xbb, 0xcc, }, 14, {}, 0 },
    };

    for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
        calcQuicksetChecksum(cases[i].command, cases[i].command_len);
        // printf("%s\n", cases[i].name);
        // tools::dump(cases[i].command, cases[i].command_len);
        dt.addTestCase(cases[i].name, cases[i].command, cases[i].command_len, NULL, 0);
    }
}/*}}}*/


int test(const char * caseName)
{
    serial::SerialDevice d("/dev/ttyUSB2");
    d.init("/dev/ttyUSB2", 9600);
    printf("fd = %d\n", d.fd());

    DeviceTestListener listener;
    DeviceTest          dt;
    dt.setListener(&listener);
    dt.setFd(d.fd());

    addV1Cases(dt);
    addV2Cases(dt);
    addQuicksetCases(dt);

    bool ret = dt.runTest(caseName);
    if (caseName != NULL && !ret) {
        printf("No such case.\n");
    }

// 
//     for (size_t i = 0; i < sizeof(a); i++) {
//         // usleep(100000);
//         int ret = write(d.fd(), &a[i], 1);
//         ret = ret;
//         printf("write %#02x\n", a[i]);
//     }
//     return 0;
// 
//     while (true) {
//         char    buffer[1024] = {0};
//         int ret = read(d.fd(), buffer, sizeof(buffer));
//         printf("received %d bytes\n", ret);
//         if (ret <= 0)
//             break;
//         tools::dump(buffer, ret);
//     }
    return 0;
}

/*{{{*/
class V2Listener : public serial::SerialDevice::Listener {
public:
    virtual void onCommand(unsigned char * command, int len) {
        tools::dump(command, len);
    }
};

int testReadAndDispatch()
{
    const unsigned char a[] = {
        0xdd, 0xff, 0x00, 0x04, 0xa1, 0x00, 0x00, 0x00, 0xbb, 0xcc,         // v2
        0xdd, 0xf2, 0x00, 0x04, 0xa1, 0x00, 0x00, 0x00, 0xbc, 0xcc,         // invalid.
        0xdd, 0xff, 0x00, 0x04, 0xa1, 0x00, 0x00, 0x00, 0xbb, 0xcc,         // v2
        0xdd, 0xff, 0x00, 0x07, 0xc1, 0x08, 0x00, 0x00, 0x00, 0x01, 0xcf, 0xbb, 0xcc,   // quickset
        0xdd, 0xff, 0x00, 0x07, 0xc1, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0xbb, 0xcc,   // invalid checksum. 
        0xdd, 0xff, 0x00, 0x06, 0xc1, 0x07, 0x00, 0x00, 0x00, 0xc0, 0xbb, 0xcc,   // quickset
    };
    FILE* fp = fopen("data.txt", "wb+");
    DCHECK(fp != NULL);
    fwrite(a, 1, sizeof(a), fp);
    fclose(fp);

    fp = fopen("v1.txt", "wb+");
    DCHECK(fp != NULL);
    fwrite(a, 1, sizeof(a), fp);
    fclose(fp);

    // Dev2 d1("v1.txt");
    // Dev2 d2("data.txt");
    // d1.init("v1.txt");
    // d2.init("data.txt");
    // d1.read();

    serial::SerialDevice d1("v1.txt");
    serial::SerialDevice d2("v1.txt");
    serial::FsmV2       fsmv2;
    serial::FsmQuickset fsmquickset;
    V2Listener          listener;
    d1.registerFsmListener(&fsmv2, &listener);
    d2.registerFsmListener(&fsmv2, &listener);
    d2.registerFsmListener(&fsmquickset, &listener);

    while (true) {
        fd_set  rfds;
        FD_ZERO(&rfds);

        int maxfd = -1;
        if (d1.fd() < 0) {
            d1.init();
        }
        if (d1.fd() >= 0) {
            FD_SET(d1.fd(), &rfds);
            maxfd = d1.fd();
        }
        if (d2.fd() < 0) {
            d2.init();
        }
        if (d2.fd() >= 0) {
            FD_SET(d2.fd(), &rfds);
            if (maxfd < d2.fd())
                maxfd = d2.fd();
        }
        if (maxfd < 0) {
            sleep(1);
            continue;
        }

        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 500000;

        int ret = select(maxfd + 1, &rfds, NULL, NULL, &tv);
        if (ret == 0)
            continue;
        if (ret < 0) {
            if (errno == EINTR)
                continue;
            ALOGD("select error: %s\n", strerror(errno));
            break;
        }
        if (FD_ISSET(d1.fd(), &rfds)) {
            d1.read();
        }
        if (FD_ISSET(d2.fd(), &rfds)) {
            d2.read();
        }
        break;
    }
    return 0;
}
/*}}}*/

int main(int argc, char * argv[])
{
    // testReadAndDispatch();

    if (argc > 1) {
        return test(argv[1]);
    }
    return test(NULL);
}

